using System.ComponentModel.DataAnnotations.Schema;
using System.Reflection;
using System.Reflection.Emit;
using Crux.Core.Data;
using NHibernate.Transform;
using NHibernate.Util;

namespace Crux.Core.NHibernate;
/// <inheritdoc/>
public class QueryRepository<TContext>(ISessionFactoryHelm<TContext> helm)
    : IQueryRepository<TContext>, IDisposableObservable, IAsyncDisposableObservable
    where TContext : NHibernateContext
{
    /// <inheritdoc/>
    public delegate TContext DynamicContextActivator(ISession session);
    /// <inheritdoc/>
    private static DynamicContextActivator? activator;
    #region private fields
    /// <inheritdoc/>
    public TContext Context => context.Value;
    /// <inheritdoc/>
    private readonly Lazy<TContext> context = new Lazy<TContext>(() =>
    {
        if (activator is null)
        {
            var sessionCtorType = new[] { typeof(ISession) };

            var dynamicMethod = new DynamicMethod("CreateInstance", typeof(TContext), sessionCtorType, true);

            var ilGenerator = dynamicMethod.GetILGenerator();
            ilGenerator.Emit(OpCodes.Nop);

            var ctor = typeof(TContext).GetConstructor(sessionCtorType);
            if (ctor is null)
                throw new MissingMemberException("UnableToFindSessionConstructor");

            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Newobj, ctor);
            ilGenerator.Emit(OpCodes.Ret);

            activator = (DynamicContextActivator)dynamicMethod.CreateDelegate(typeof(DynamicContextActivator));
        }

        var session = helm.GetSession();

        return activator(session);
    });
    #endregion  private fields
    #region implements IDisposable
    /// <inheritdoc/>
    public bool IsDisposed { get; private set; }

    /// <inheritdoc/>
    public void Dispose()
    {
        if (IsDisposed) return;

        IsDisposed = true;

        if (context.IsValueCreated)
        {
            using var _ = context.Value;
        }

        GC.SuppressFinalize(this);
    }

    /// <inheritdoc/>
    public ValueTask DisposeAsync()
    {
        var completed = new ValueTask(Task.CompletedTask);
        if (IsDisposed)
        {
            return completed;
        }

        IsDisposed = true;
        if (context.IsValueCreated)
        {
            using var _ = context.Value;
        }

        GC.SuppressFinalize(this);

        return completed;
    }
    #endregion implements IDisposable

    #region implements IQueryRepository
    /// <inheritdoc/>
    public bool Any<T>(
        Expression<Func<T, bool>> predicate,
        bool ignoreFilter = false) where T : class
    {
        Requires.NotNull(predicate, nameof(predicate));

        return Query<T>(ignoreFilter: ignoreFilter)
            .Where(predicate)
            .Skip(0)
            .Take(1)
            .Any();
    }

    /// <inheritdoc/>
    public async Task<bool> AnyAsync<T>(
        Expression<Func<T, bool>> predicate,
        bool ignoreFilter = false,
        CancellationToken cancellationToken = default) where T : class
    {
        Requires.NotNull(predicate, nameof(predicate));

        return await Query<T>(ignoreFilter: ignoreFilter)
            .Where(predicate)
            .Skip(0)
            .Take(1)
            .AnyAsync(cancellationToken).ConfigureAwait(false);
    }
    /// <inheritdoc/>
    public T FirstOrDefault<T>(
        Expression<Func<T, bool>> predicate,
        bool ignoreFilter = false) where T : class
        => Query<T>(ignoreFilter: ignoreFilter)
            .Where(predicate)
            .FirstOrDefault();
    /// <inheritdoc/>
    public async Task<T> FirstOrDefaultAsync<T>(
        Expression<Func<T, bool>> predicate,
        bool ignoreFilter = false,
        CancellationToken cancellationToken = default) where T : class
        => await Query<T>(ignoreFilter: ignoreFilter).
            Where(predicate)
            .FirstOrDefaultAsync(cancellationToken)
            .ConfigureAwait(false);
    /// <inheritdoc/>
    public int Count<T>(
        Expression<Func<T, bool>> predicate,
        bool ignoreFilter = false) where T : class
    {
        Requires.NotNull(predicate, nameof(predicate));

        return this.Query<T>(ignoreFilter: ignoreFilter)
            .Where(predicate)
            .Count();
    }

    /// <inheritdoc/>
    public async Task<int> CountAsync<T>(
        Expression<Func<T, bool>> predicate,
        bool ignoreFilter = false,
        CancellationToken cancellationToken = default) where T : class
    {
        Requires.NotNull(predicate, nameof(predicate));

        return await this.Query<T>(ignoreFilter: ignoreFilter)
            .Where(predicate)
            .CountAsync(cancellationToken)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public long LongCount<T>(
        Expression<Func<T, bool>> predicate,
        bool ignoreFilter = false) where T : class
    {
        Requires.NotNull(predicate, nameof(predicate));

        return this.Query<T>(ignoreFilter: ignoreFilter)
            .Where(predicate)
            .LongCount();
    }

    /// <inheritdoc/>
    public async Task<long> LongCountAsync<T>(
        Expression<Func<T, bool>> predicate,
        bool ignoreFilter = false,
        CancellationToken cancellationToken = default) where T : class
    {
        Requires.NotNull(predicate, nameof(predicate));

        return await this.Query<T>(ignoreFilter: ignoreFilter)
            .Where(predicate)
            .LongCountAsync(cancellationToken)
            .ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public T Find<T, TPrimary>(TPrimary primary) where T : class, IGenericEntity<TPrimary>
    {
        return Context.Get<T>(primary);
    }

    /// <inheritdoc/>
    public T Find<T>(object id) where T : class
    {
        return Context.Get<T>(id);
    }
    /// <inheritdoc/>
    public ISQLQuery FromSqlRaw<TEntity>(
        string sql) where TEntity : class
    {
        var query = Context.CreateSQLQuery(sql);

        var bean = Transformers.AliasToBean<TEntity>();

        query.SetResultTransformer(bean);

        var propertyInfos = typeof(TEntity).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        foreach (var property in propertyInfos)
        {
            var columnAttribute = property.GetCustomAttribute<ColumnAttribute>();
            if (columnAttribute is null)
            {
                continue;
            }

            var name = columnAttribute.Name;
            if (string.IsNullOrEmpty(name))
            {
                continue;
            }

            var propertyType = property.PropertyType;
            if (propertyType.IsNullable())
            {
                propertyType = Nullable.GetUnderlyingType(propertyType);
            }

            if (propertyType is null)
            {
                continue;
            }

            if (propertyType.IsEnum)
            {
                query.AddScalar(name, NHibernateUtil.Enum(propertyType));
            }

            else if (NHibernateConstants.TypeMapping.TryGetValue(propertyType, out var type))
            {
                query.AddScalar(name, type);
            }
        }

        return query;
    }

    /// <inheritdoc/>
    private IOrderedQueryable<T> GetOrderedQuery<T>(
        IEnumerable<OrderExpression<T>> expressions,
        IQueryable<T> query)
    {
        Requires.NotNullOrEmpty(expressions, nameof(expressions));

        var firstExpression = expressions.FirstOrDefault();
        if (firstExpression?.Expression is null)
            throw new ArgumentNullException(nameof(firstExpression.Expression));

        var orderQuery = firstExpression.Symbol switch
        {
            OrderSymbol.Asc => query.OrderBy(firstExpression.Expression),
            _ => query.OrderByDescending(firstExpression.Expression)
        };

        foreach (var expression in expressions)
        {
            if (firstExpression == expression)
            {
                continue;
            }

            if (expression.Expression is null)
            {
                throw new ArgumentNullException(nameof(expression.Expression));
            }

            orderQuery = expression.Symbol switch
            {
                OrderSymbol.Asc => orderQuery.ThenBy(firstExpression.Expression),
                _ => orderQuery.ThenByDescending(firstExpression.Expression)
            };
        }

        return orderQuery;
    }

    /// <inheritdoc/>
    public Pageable<T> GetByPage<T>(
        IEnumerable<OrderExpression<T>> orderExpressions,
        Expression<Func<T, bool>>? where = null,
        PageOption option = default) where T : class
    {
        Requires.NotNull(orderExpressions, nameof(orderExpressions));

        var query = where is null ? Query<T>() : Query<T>().Where(where);
        query = GetOrderedQuery(orderExpressions, query);

        if (option.PageSize.HasValue &&
            option.PageIndex.HasValue)
        {
            if (0 == option.PageSize)
            {
                query = query.Where(p => false);
                return new Pageable<T>(query, option, 0);
            }
            else
            {
                int totalCount = query.Count();
                query = query.Skip((option.PageIndex.Value - 1) * option.PageSize.Value).Take(option.PageSize.Value);

                return new Pageable<T>(query, option, totalCount);
            }
        }
        else
        {
            int totalCount = query.Count();

            return new Pageable<T>(query, 1, totalCount, totalCount);
        }
    }


    /// <inheritdoc/>
    public async Task<Pageable<T>> GetByPageAsync<T>(
        IEnumerable<OrderExpression<T>> orderExpressions,
        Expression<Func<T, bool>>? where = null,
        PageOption option = default,
        CancellationToken cancellationToken = default) where T : class
     => await Task.Run(() => GetByPage(orderExpressions, where, option), cancellationToken).ConfigureAwait(false);

    /// <inheritdoc/>
    public IQueryable<T> Query<T>(bool noTracking = true, bool ignoreFilter = false) where T : class
    {
        if (ignoreFilter)
        {
            Context.DisableFilter(NHibernateConstants.SoftDeleteConstants);
        }

        return Context.Query<T>();
    }


    #endregion implements IQueryRepository
}